/**
 * @file main.c
 * @author Gvozdarev Sergey, gr016 MIPT
 * MIT license
 */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include "mpi.h"

#define __DEBUG
#define MAT_SIZE 500

#define G_ELEM_NUM 1
#define G_FINISH   -1
#define G_MAIN_RANK 0

#define  ijToN(i,j) ((i-1)*MAT_SIZE+j-1)
#define  NToi(N) (N/MAT_SIZE+1)
#define  NToj(N) (N%MAT_SIZE+1)


main( int argc, char ** argv )  
{
    double ttotal_begin, ttotal_end;
    double tinit_begin, tinit_end;
    double tbcast_begin, tbcast_end;
    double tcompute_begin, tcompute_end;
    

    
	int comm_rank,comm_size,num_of_workers;
    MPI_Status status;

	//// Init
    MPI_Init( &argc, &argv ); 
    ttotal_begin = tinit_begin = MPI_Wtime();

	MPI_Comm_rank( MPI_COMM_WORLD, &comm_rank );
    MPI_Comm_size( MPI_COMM_WORLD, &comm_size );
    
    if ( comm_size == 1)
    {
        printf("comm_size must be bigger than 1");
        MPI_Finalize();
        return 0;
    }
    
    if ( comm_rank == 0) // main process
    {
        srandom( getpid());
  

#ifdef __DEBUG




#endif
        tinit_end = tbcast_begin = MPI_Wtime();
		
        MPI_Bcast( mat1, MAT_SIZE*MAT_SIZE, MPI_DOUBLE, G_MAIN_RANK,MPI_COMM_WORLD );
		MPI_Bcast( mat2, MAT_SIZE*MAT_SIZE, MPI_DOUBLE, G_MAIN_RANK,MPI_COMM_WORLD );
        
        tbcast_end = tcompute_begin = MPI_Wtime();
    
        
        int current = 0;
        int progress = 1;
        int i;

        for ( i = 1; i <= num_of_workers; i++)
        {
            MPI_Send( &current, 1, MPI_INT, i, G_ELEM_NUM, MPI_COMM_WORLD);// send num of element to compute
            current++;
        }
        
        do
        {
            double res;
            MPI_Recv( &res, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG,  MPI_COMM_WORLD,&status); // recieve result
            progress++;
            
            mat3[ status.MPI_TAG] = res;
            if( current < MAT_SIZE*MAT_SIZE)
            {
                MPI_Send( &current,1, MPI_INT, status.MPI_SOURCE , G_ELEM_NUM,MPI_COMM_WORLD );// send new task 
                current++;
            }
        }while ( progress <= MAT_SIZE*MAT_SIZE);
        
        for ( i = 1; i < comm_size; i++)
        {
            int n = G_FINISH;
            MPI_Send( &n, 1, MPI_INT, i, G_ELEM_NUM, MPI_COMM_WORLD);
        }
        
    }else 
    { // workers
		MPI_Bcast( mat1, MAT_SIZE*MAT_SIZE, MPI_DOUBLE, G_MAIN_RANK,MPI_COMM_WORLD );
		MPI_Bcast( mat2, MAT_SIZE*MAT_SIZE, MPI_DOUBLE, G_MAIN_RANK,MPI_COMM_WORLD );
        
        int n;
        do
        {
            MPI_Recv( &n, 1, MPI_INT, G_MAIN_RANK, G_ELEM_NUM, MPI_COMM_WORLD, &status); // recieve num of elements
            if ( n == G_FINISH)
            {
                MPI_Barrier( MPI_COMM_WORLD);
                MPI_Finalize();
                return 0;
            }
            int k;
            int i = NToi( n);
            int j = NToj( n);
            double res = 0;
            for ( k = 1; k <= MAT_SIZE; k++ )
            {
                res += mat1[ ijToN( i,k)] * mat2[ ijToN( k,j)];
            }
            MPI_Send( &res, 1,MPI_DOUBLE, G_MAIN_RANK, n, MPI_COMM_WORLD);
        }
        while(1);
    }
#ifdef __DEBUG
        printf("Resutlt\n");
        printMat( mat3);
#endif

    ttotal_end = tcompute_end = MPI_Wtime();

    printf( "--------------------------------\n");
    printf("Matrix size %d * %d\n",MAT_SIZE,MAT_SIZE);
    printf("Number of workers %d\n",num_of_workers);
    printf( "--------------------------------\n");
    printf( "|| Init time     \t%.2f ms \n", ( tinit_end  - tinit_begin)*1000);
    printf( "|| Broadcast time\t%.2f ms \n", ( tbcast_end - tbcast_begin)*1000);
    printf( "|| Computing time\t%.2f ms \n", ( tcompute_end - tcompute_begin)*1000);
    printf( "--------------------------------\n");
    printf( "|| Total time    \t%.2f ms \n", ( ttotal_end - ttotal_begin)*1000);
    printf( "--------------------------------\n");
    
    MPI_Barrier( MPI_COMM_WORLD);
    MPI_Finalize();
} 
